package com.atguigu.gmall.activity.service.impl;

import com.atguigu.gmall.activity.model.OrderRecode;
import com.atguigu.gmall.activity.model.SeckillGoods;
import com.atguigu.gmall.activity.mapper.SeckillGoodsMapper;
import com.atguigu.gmall.activity.model.UserRecode;
import com.atguigu.gmall.activity.service.SeckillGoodsService;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.rabbit.config.MqConst;
import com.atguigu.gmall.common.rabbit.util.RabbitService;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.DateUtil;
import com.atguigu.gmall.common.util.MD5;
import com.atguigu.gmall.order.client.OrderFeignClient;
import com.atguigu.gmall.order.model.OrderDetail;
import com.atguigu.gmall.order.model.OrderInfo;
import com.atguigu.gmall.user.client.UserFeignClient;
import com.atguigu.gmall.user.model.UserAddress;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.benmanes.caffeine.cache.Cache;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Array;
import java.util.*;

/**
 * 业务实现类
 *
 * @author atguigu
 * @since 2023-06-30
 */
@Slf4j
@Service
public class SeckillGoodsServiceImpl extends ServiceImpl<SeckillGoodsMapper, SeckillGoods> implements SeckillGoodsService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private Cache<String, String> cache;


    @Autowired
    private RabbitService rabbitService;

    @Autowired
    private UserFeignClient userFeignClient;

    @Autowired
    private OrderFeignClient orderFeignClient;


    /**
     * 查询当日参与秒杀商品
     *
     * @return
     */
    @Override
    public List<SeckillGoods> getSeckillGoods() {
        BoundHashOperations<String, String, SeckillGoods> seckillHashOps = getSeckillGoodsHashOpes();
        return seckillHashOps.values();
    }


    /**
     * 返回操作秒杀商品对象
     *
     * @return
     */
    private BoundHashOperations<String, String, SeckillGoods> getSeckillGoodsHashOpes() {
        String seckillKey = RedisConst.SECKILL_GOODS;
        BoundHashOperations<String, String, SeckillGoods> seckillHashOps = redisTemplate.boundHashOps(seckillKey);
        return seckillHashOps;
    }

    /**
     * 查询秒杀商品详情
     *
     * @param skuId
     * @return
     */
    @Override
    public SeckillGoods getSecGoodsById(Long skuId) {
        BoundHashOperations<String, String, SeckillGoods> hashOpes = getSeckillGoodsHashOpes();
        String hashKey = skuId.toString();
        Boolean flag = hashOpes.hasKey(hashKey);
        if (flag) {
            return hashOpes.get(hashKey);
        }
        return null;
    }

    /**
     * 获取抢购码
     *
     * @param userId
     * @param skuId
     * @return
     */
    @Override
    public String getSeckillSkuIdStr(String userId, Long skuId) {
        //1.从本地缓存中获取商品状态 前提:状态必须是"1"
        String states = cache.getIfPresent(skuId.toString());
        if (StringUtils.isBlank(states)) {
            throw new RuntimeException("参数异常!");
        }
        if ("0".equals(states)) {
            throw new RuntimeException("抢购已结束!");
        }
        //2.从分布式缓存中获取秒杀商品信息得到开始时跟结束时间
        if ("1".equals(states)) {
            SeckillGoods seckillGoods = this.getSecGoodsById(skuId);
            if (seckillGoods != null) {
                Date now = new Date();
                Date startTime = seckillGoods.getStartTime();
                Date endTime = seckillGoods.getEndTime();
                if (DateUtil.dateCompare(startTime, now) && DateUtil.dateCompare(now, endTime)) {
                    //3.生成抢购码 规则:md5(用户ID+商品ID)  todo 考虑将抢购码放入分布式缓存
                    String buyCode = MD5.encrypt(userId + skuId);
                    return buyCode;
                }
            }
        }
        throw new RuntimeException("参数异常!");
    }

    /**
     * 秒杀下单预处理,目的采用MQ进行流量削峰,经过业务校验后将秒杀请求放入MQ中,将来异步处理秒杀
     *
     * @param userId  用户ID
     * @param skuId   秒杀商品ID
     * @param buyCode 抢购码
     */
    @Override
    public void seckillReq2Queue(String userId, Long skuId, String buyCode) {
        //1.验证用户提交抢购码是否正确
        //1.1 采用相同方式生成抢购码
        String encrypt = MD5.encrypt(userId + skuId);
        //1.2 跟用户提交比较
        if (!buyCode.equals(encrypt)) {
            throw new RuntimeException("抢购码错误,请重试!");
        }

        //2.查询本地缓存中商品状态 验证是否在售卖中
        String state = cache.getIfPresent(skuId.toString());
        if (StringUtils.isBlank(state)) {
            throw new RuntimeException("抢购商品不存在!");
        }
        if ("0".equals(state)) {
            throw new RuntimeException("商品已售罄!");
        }
        //3.构建用户秒杀商品购买意向发送到MQ
        if ("1".equals(state)) {
            //3.1 构建秒杀请求对象
            UserRecode userRecode = new UserRecode();
            userRecode.setUserId(userId);
            userRecode.setSkuId(skuId);
            //3.2 发送消息到MQ
            rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_SECKILL_USER, MqConst.ROUTING_SECKILL_USER, userRecode);
        }
    }

    /**
     * 秒杀核心业务处理:从MQ中处理秒杀请求
     *
     * @param userRecode
     */
    @Override
    public void processSeckill(UserRecode userRecode) {
        // 1.从本地缓存中获取商品状态位 0:秒杀售罄 1:秒杀中
        String state = cache.getIfPresent(userRecode.getSkuId().toString());
        if (StringUtils.isBlank(state) || "0".equals(state)) {
            return;
        }
        // 2.判断用户是否重复下单 hash结构   key:(seckill:orders:users) hashKey:(useId+skuId) hashvale:(订单ID)
        String seckillUserOrdersKey = RedisConst.SECKILL_ORDERS_USERS;
        BoundHashOperations<String, String, Long> userOrderHashOps = redisTemplate.boundHashOps(seckillUserOrdersKey);
        String hashKey = userRecode.getUserId() + userRecode.getSkuId();
        if (userOrderHashOps.hasKey(hashKey)) {
            //用户重复下单 直接返回
            return;
        }
        // 3.尝试从Redis中List结构中弹出数据. 验证库存同时redis库存也会被扣减
        String stockListKey = RedisConst.SECKILL_STOCK_PREFIX + userRecode.getSkuId();
        BoundListOperations<String, String> boundListOperations = redisTemplate.boundListOps(stockListKey);
        String hashStockStr = boundListOperations.rightPop();
        if (StringUtils.isBlank(hashStockStr)) {
            // 3.2失败:秒杀库存为空-立即发送消息到Redis(发布订阅) 更新本地缓存
            redisTemplate.convertAndSend("seckillpush", userRecode.getSkuId() + ":0");
            return;
        }
        // 3.1成功:获取秒杀资格
        log.info("[秒杀服务]秒杀业务处理,获取到秒杀资格:{}", userRecode);
        // 4.生成秒杀临时订单-存入临时hash
        // 4.1 创建临时订单hash操作对象
        String seckillTempOrdersKey = RedisConst.SECKILL_ORDERS;
        BoundHashOperations<String, String, OrderRecode> tempOrderHashOps = redisTemplate.boundHashOps(seckillTempOrdersKey);
        // 4.2 构建临时订单对象
        OrderRecode orderRecode = new OrderRecode();
        orderRecode.setUserId(userRecode.getUserId());
        orderRecode.setSeckillGoods(this.getSecGoodsById(userRecode.getSkuId()));
        orderRecode.setNum(1);
        orderRecode.setOrderStr(UUID.randomUUID().toString().replaceAll("-", ""));
        // 4.3 存储临时订单到redis
        tempOrderHashOps.put(hashKey, orderRecode);

        // 5.发送更新库存消息到MQ异步更新库存
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_SECKILL_STOCK, MqConst.ROUTING_SECKILL_STOCK, userRecode);
    }


    /**
     * 扣减秒杀商品库存
     *
     * @param userRecode
     */
    @Override
    public void processDedcutStock(UserRecode userRecode) {
        //1.查询Redis中List商品库存数量
        String stockListKey = RedisConst.SECKILL_STOCK_PREFIX + userRecode.getSkuId();
        BoundListOperations<String, String> stockListOps = redisTemplate.boundListOps(stockListKey);
        Long stockCount = stockListOps.size();

        //2.修改Redis中hash秒杀商品信息中库存数量
        String seckillGoodsKey = RedisConst.SECKILL_GOODS;
        BoundHashOperations<String, String, SeckillGoods> seckillGoodsHashOps = redisTemplate.boundHashOps(seckillGoodsKey);
        String hashKey = userRecode.getSkuId().toString();
        if (seckillGoodsHashOps.hasKey(hashKey)) {
            SeckillGoods seckillGoods = seckillGoodsHashOps.get(hashKey);
            seckillGoods.setStockCount(stockCount.intValue());
            seckillGoodsHashOps.put(hashKey, seckillGoods);


            //3.修改MySQL数据库中库存数量
            this.updateById(seckillGoods);
        }
    }


    /**
     * 检查用户秒杀下单结果
     *
     * @param skuId
     * @return
     */
    @Override
    public Result checkSeckillResult(String userId, Long skuId) {
        //1.如果用户在排队-有机会获取秒杀资格响应211
        String ifQueueKey = "seckill:" + userId + ":" + skuId + ":req";
        Boolean ifQueue = redisTemplate.hasKey(ifQueueKey);
        if (ifQueue) {
            //2.判断用户是否产生秒杀订单-响应218
            String seckillUserOrdersKey = RedisConst.SECKILL_ORDERS_USERS;
            BoundHashOperations<String, String, Long> userOrderHashOps = redisTemplate.boundHashOps(seckillUserOrdersKey);
            String hashKey = userId + skuId;
            if (userOrderHashOps.hasKey(hashKey)) {
                // 响应秒杀订单ID,进入支付页面
                Long orderId = userOrderHashOps.get(hashKey);
                return Result.build(orderId, ResultCodeEnum.SECKILL_ORDER_SUCCESS);
            }

            //3.判断用户是否产生临时订单-响应215
            String seckillTempOrdersKey = RedisConst.SECKILL_ORDERS;
            BoundHashOperations<String, String, OrderRecode> tempOrderHashOps = redisTemplate.boundHashOps(seckillTempOrdersKey);
            if (tempOrderHashOps.hasKey(hashKey)) {
                //相应业务数据为临时订单数据,进入秒杀订单确认页面
                OrderRecode orderRecode = tempOrderHashOps.get(hashKey);
                return Result.build(orderRecode, ResultCodeEnum.SECKILL_SUCCESS);
            }

            //排队中,但是没有订单产生,没有临时订单 页面继续显示排队中
            return Result.build(null, ResultCodeEnum.SECKILL_RUN);
        }
        //4.如果用户未在排队且本地缓存中商品状态为"0"-响应213
        if (!ifQueue) {
            String state = cache.getIfPresent(skuId.toString());
            if (StringUtils.isBlank(state) || "0".equals(state)) {
                return Result.build(null, ResultCodeEnum.SECKILL_FINISH);
            }
        }
        return Result.build(null, ResultCodeEnum.SECKILL_RUN);
    }

    /**
     * 查询用户秒杀得到订单明细
     *
     * @param userId
     * @param skuId
     * @return
     */
    @Override
    public List<OrderDetail> getSeckillOrderDetail(String userId, Long skuId) {
        //1.根据用户ID+商品SKuID查询Redis临时订单中商品信息
        String seckillTempOrdersKey = RedisConst.SECKILL_ORDERS;
        BoundHashOperations<String, String, OrderRecode> tempOrderHashOps = redisTemplate.boundHashOps(seckillTempOrdersKey);
        String hashKey = userId + skuId;
        if (tempOrderHashOps.hasKey(hashKey)) {
            OrderRecode orderRecode = tempOrderHashOps.get(hashKey);
            if (orderRecode != null) {
                SeckillGoods seckillGoods = orderRecode.getSeckillGoods();
                if (seckillGoods != null) {
                    //将秒杀商品对象转为订单明细对象
                    OrderDetail orderDetail = new OrderDetail();
                    orderDetail.setImgUrl(seckillGoods.getSkuDefaultImg());
                    orderDetail.setSkuName(seckillGoods.getSkuName());
                    orderDetail.setSkuId(seckillGoods.getSkuId());
                    orderDetail.setSkuNum(orderRecode.getNum());
                    orderDetail.setOrderPrice(seckillGoods.getCostPrice());
                    return Arrays.asList(orderDetail);
                }
            }
        }
        return null;
    }

    /**
     * 汇总秒杀订单页面数据
     *
     * @param userId
     * @param skuId
     * @return
     */
    @Override
    public Map<String, Object> seckillOrderTrade(String userId, Long skuId) {
        HashMap<String, Object> mapResult = new HashMap<>();
        //${userAddressList}:地址列表
        List<UserAddress> userAddressList = userFeignClient.getUserAddressListByUserId(Long.valueOf(userId));
        mapResult.put("userAddressList", userAddressList);
        //${detailArrayList}:订单明细列表秒杀商品明细
        List<OrderDetail> orderDetailList = this.getSeckillOrderDetail(userId, skuId);
        mapResult.put("detailArrayList", orderDetailList);


        //${totalAmount}:总金额
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setOrderDetailList(orderDetailList);
        orderInfo.sumTotalAmount();
        mapResult.put("totalAmount", orderInfo.getTotalAmount());

        //${totalNum}:总数
        mapResult.put("totalNum", orderDetailList.size());
        return mapResult;
    }

    /**
     * 保存秒杀订单业务逻辑
     *
     * @param orderInfo
     * @return
     */
    @Override
    public Long submitSeckillOrder(OrderInfo orderInfo) {
        //1.远程调用订单微服务保存订单 获取订单ID
        Long orderId = orderFeignClient.submitSeckillOrder(orderInfo);
        //2.将当前用户秒杀过程中产生临时订单删除
        String seckillTempOrdersKey = RedisConst.SECKILL_ORDERS;
        BoundHashOperations<String, String, OrderRecode> tempOrderHashOps = redisTemplate.boundHashOps(seckillTempOrdersKey);
        List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
        String hashKey = orderInfo.getUserId() + orderDetailList.get(0).getSkuId().toString();
        if (!CollectionUtils.isEmpty(orderDetailList)) {
            tempOrderHashOps.delete(hashKey);
        }
        //3.记录用户秒杀下单存入Redis中(避免重复下单)
        String seckillUserOrdersKey = RedisConst.SECKILL_ORDERS_USERS;
        BoundHashOperations<String, String, Long> userOrderHashOps = redisTemplate.boundHashOps(seckillUserOrdersKey);
        userOrderHashOps.put(hashKey, orderId);
        //todo 发送延迟消息 5分钟后查询订单支付状态 根据支付结果是否进行回滚秒杀库存(本地缓存同样修改)
        return orderId;
    }


    /**
     * 清理当日秒杀产生缓存
     */
    @Override
    public void processCleanCache() {
        //1.清理分布式缓存中redis数据
        Set<String> keys = redisTemplate.keys("seckill:" + "*");
        redisTemplate.delete(keys);
        //2.清理本地缓存 TODO 采用Redis发布订阅 通知所有秒杀服务实例节点 清理各自本地缓存
        cache.invalidateAll();
    }
}
